package zhishu.iceyyy.com.bxq_2500.ui;

import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.Scroller;

import java.util.LinkedList;
import java.util.Queue;

/**
 * 横向滑动的ListView(滑动和点击冲突已经解决)
 */
public class HorizontalListView extends AdapterView<ListAdapter> { float startX;
    float startY;

    protected ListAdapter mAdapter;
    private int mLeftViewIndex = -1;
    private int mRightViewIndex = 0;
    protected int mCurrentX;
    protected int mNextX;
    private int mMaxX = Integer.MAX_VALUE;
    private int mDisplayOffset = 0;
    protected Scroller mScroller;
    private GestureDetector mGesture;
    private Queue<View> mRemovedViewQueue = new LinkedList<View>();
    private OnItemSelectedListener mOnItemSelected;
    private OnItemClickListener mOnItemClicked;
    private OnItemLongClickListener mOnItemLongClicked;
    private boolean mDataChanged = false;


    public HorizontalListView(Context context, AttributeSet attrs) { super(context, attrs);
        initView();
    } private synchronized void initView() { mLeftViewIndex = -1;
        mRightViewIndex = 0;
        mDisplayOffset = 0;
        mCurrentX = 0;
        mNextX = 0;
        mMaxX = Integer.MAX_VALUE;
        mScroller = new Scroller(getContext());
        mGesture = new GestureDetector(getContext(), mOnGesture);
    } @Override
    public void setOnItemSelectedListener(OnItemSelectedListener listener) { mOnItemSelected = listener;
    } @Override
    public void setOnItemClickListener(OnItemClickListener listener) { mOnItemClicked = listener;
    } @Override
    public void setOnItemLongClickListener(OnItemLongClickListener listener) { mOnItemLongClicked = listener;
    } private DataSetObserver mDataObserver = new DataSetObserver() { @Override
    public void onChanged() { synchronized (HorizontalListView.this) { mDataChanged = true;
    } invalidate();
        requestLayout();
    } @Override
    public void onInvalidated() { reset();
        invalidate();
        requestLayout();
    } };

    @Override
    public ListAdapter getAdapter() { return mAdapter;
    } @Override
    public View getSelectedView() { return null;
    } @Override
    public void setAdapter(ListAdapter adapter) { if (mAdapter != null) { mAdapter.unregisterDataSetObserver(mDataObserver);
    } mAdapter = adapter;
        mAdapter.registerDataSetObserver(mDataObserver);
        reset();
    } private synchronized void reset() { initView();
        removeAllViewsInLayout();
        requestLayout();
    } @Override
    public void setSelection(int position) { } private void addAndMeasureChild(final View child, int viewPos) { LayoutParams params = child.getLayoutParams();
        if (params == null) { params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
        } addViewInLayout(child, viewPos, params, true);
        child.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
                MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
    } @Override
    protected synchronized void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom);

        if (mAdapter == null) { return;
        } if (mDataChanged) { int oldCurrentX = mCurrentX;
            initView();
            removeAllViewsInLayout();
            mNextX = oldCurrentX;
            mDataChanged = false;
        } if (mScroller.computeScrollOffset()) { int scrollx = mScroller.getCurrX();
            mNextX = scrollx;
        } if (mNextX <= 0) { mNextX = 0;
            mScroller.forceFinished(true);
        } if (mNextX >= mMaxX) { mNextX = mMaxX;
            mScroller.forceFinished(true);
        } int dx = mCurrentX - mNextX;

        removeNonVisibleItems(dx);
        fillList(dx);
        positionItems(dx);

        mCurrentX = mNextX;

        if (!mScroller.isFinished()) { post(new Runnable() { @Override
        public void run() { requestLayout();
        } });
        } } private void fillList(final int dx) { int edge = 0;
        View child = getChildAt(getChildCount() - 1);
        if (child != null) { edge = child.getRight();
        } fillListRight(edge, dx);

        edge = 0;
        child = getChildAt(0);
        if (child != null) { edge = child.getLeft();
        } fillListLeft(edge, dx);
    } private void fillListRight(int rightEdge, final int dx) { while (rightEdge + dx < getWidth() && mRightViewIndex < mAdapter.getCount()) { View child = mAdapter.getView(mRightViewIndex, mRemovedViewQueue.poll(), this);
        addAndMeasureChild(child, -1);
        rightEdge += child.getMeasuredWidth();

        if (mRightViewIndex == mAdapter.getCount() - 1) { mMaxX = mCurrentX + rightEdge - getWidth();
        } if (mMaxX < 0) { mMaxX = 0;
        } mRightViewIndex++;
    } } private void fillListLeft(int leftEdge, final int dx) { while (leftEdge + dx > 0 && mLeftViewIndex >= 0) { View child = mAdapter.getView(mLeftViewIndex, mRemovedViewQueue.poll(), this);
        addAndMeasureChild(child, 0);
        leftEdge -= child.getMeasuredWidth();
        mLeftViewIndex--;
        mDisplayOffset -= child.getMeasuredWidth();
    } } private void removeNonVisibleItems(final int dx) { View child = getChildAt(0);
        while (child != null && child.getRight() + dx <= 0) { mDisplayOffset += child.getMeasuredWidth();
            mRemovedViewQueue.offer(child);
            removeViewInLayout(child);
            mLeftViewIndex++;
            child = getChildAt(0);

        } child = getChildAt(getChildCount() - 1);
        while (child != null && child.getLeft() + dx >= getWidth()) { mRemovedViewQueue.offer(child);
            removeViewInLayout(child);
            mRightViewIndex--;
            child = getChildAt(getChildCount() - 1);
        } } private void positionItems(final int dx) { if (getChildCount() > 0) { mDisplayOffset += dx;
        int left = mDisplayOffset;
        for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i);
            int childWidth = child.getMeasuredWidth();
            child.layout(left, 0, left + childWidth, child.getMeasuredHeight());
            left += childWidth + child.getPaddingRight();
        } } } public synchronized void scrollTo(int x) { mScroller.startScroll(mNextX, 0, x - mNextX, 0);
        requestLayout();
    } /**
     * 上下滑动需要拦截，
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) { boolean handled = super.dispatchTouchEvent(ev);
        handled |= mGesture.onTouchEvent(ev);
        getParent().requestDisallowInterceptTouchEvent(true);

        switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: startX = (int) ev.getX();
            startY = (int) ev.getY();
            break;
            case MotionEvent.ACTION_MOVE: int endX = (int) ev.getX();
                int endY = (int) ev.getY();

                int dx = (int) (endX - startX);
                int dy = (int) (endY - startY);

                if (Math.abs(dx) + 50 > Math.abs(dy)) { } else { // 上下滑动,需要拦截
                    getParent().requestDisallowInterceptTouchEvent(false);
                } break;
            case MotionEvent.ACTION_UP: break;
        } return handled | super.dispatchTouchEvent(ev);
    } protected boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                                float velocityY) { synchronized (HorizontalListView.this) { mScroller.fling(mNextX, 0, (int) -velocityX, 0, 0, mMaxX, 0, 0);
    } requestLayout();
        return true;
    } protected boolean onDown(MotionEvent e) { mScroller.forceFinished(true);
        return true;
    } private OnGestureListener mOnGesture = new GestureDetector.SimpleOnGestureListener() { @Override
    public boolean onDown(MotionEvent e) { return HorizontalListView.this.onDown(e);
    } @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                           float velocityY) { return HorizontalListView.this.onFling(e1, e2, velocityX, velocityY);
    } @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2,
                            float distanceX, float distanceY) { synchronized (HorizontalListView.this) { mNextX += (int) distanceX;
    } requestLayout();
        return true;
    } @Override
    public boolean onSingleTapConfirmed(MotionEvent e) { for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i);
        if (isEventWithinView(e, child)) { if (mOnItemClicked != null) { mOnItemClicked.onItemClick(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId(mLeftViewIndex + 1 + i));
        } if (mOnItemSelected != null) { mOnItemSelected.onItemSelected(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId(mLeftViewIndex + 1 + i));
        } break;
        } } return true;
    } @Override
    public void onLongPress(MotionEvent e) { int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) { View child = getChildAt(i);
            if (isEventWithinView(e, child)) { if (mOnItemLongClicked != null) { mOnItemLongClicked.onItemLongClick(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId(mLeftViewIndex + 1 + i));
            } break;
            } } } private boolean isEventWithinView(MotionEvent e, View child) { Rect viewRect = new Rect();
        int[] childPosition = new int[2];
        child.getLocationOnScreen(childPosition);
        int left = childPosition[0];
        int right = left + child.getWidth();
        int top = childPosition[1];
        int bottom = top + child.getHeight();
        viewRect.set(left, top, right, bottom);
        return viewRect.contains((int) e.getRawX(), (int) e.getRawY());
    } };

    //解决滑动和点击冲突问题
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: startX = ev.getX();
        startY = ev.getY();
        break;
        case MotionEvent.ACTION_MOVE: float endX = ev.getX();
            float endY = ev.getY();

            float dx = endX - startX;
            float dy = endY - startY;

            if (Math.abs(dx) - 10 > Math.abs(dy)) { return true;
            } else if (Math.abs(dx) < Math.abs(dy) - 10) { return true;
            } break;
        case MotionEvent.ACTION_UP: break;
    } return super.onInterceptTouchEvent(ev);
    } }
